﻿using System;
using System.Text.RegularExpressions;

namespace Kyoh.Lib.Text
{
    /// <summary><see cref="Kyoh.Lib.Text.Wildcard.Replace(string, WildMatchEvaluator)"/> メソッドの操作中に、ワイルドカードとの一致が見つかるたびに呼び出されるメソッドを表します。</summary>
    /// <param name="match"><see cref="Kyoh.Lib.Text.Wildcard.Replace(string, WildMatchEvaluator)"/> メソッドの操作中に、1 つのワイルドカードで見つかった一致を表す <see cref="WildMatch"/> オブジェクト。</param>
    /// <returns><see cref="WildMatchEvaluator"/> デリゲートが表すメソッドによって返された文字列。</returns>
    [Serializable]
    public delegate string WildMatchEvaluator(WildMatch match);
    /// <summary>変更不可のワイルドカードを表します。</summary>
    public class Wildcard : IPattern
    {
        private readonly string _replacement;
        private readonly string _pattern;
        private readonly Regex _bodyRegex;
        private static readonly Regex WildToRegex = new Regex(@"[\\\+\|\{\[\(\)\^\$\.\#]|(?<!~)(?:~~)*[\*\?~]", RegexOptions.Compiled);
        private static readonly Regex UnescapeRegexChilda = new Regex(@"~\\([\*\?\~])", RegexOptions.Compiled);
        private static readonly Regex UnescapeChilda = new Regex(@"~([\*\?\~])", RegexOptions.Compiled);
        private static readonly Regex EscapeChilda = new Regex(@"([\*\?\~])", RegexOptions.Compiled);

        /// <summary>指定したワイルドカードの <see cref="Wildcard"/> クラスの新しいインスタンスを初期化およびコンパイルします。</summary>
        /// <param name="pattern">一致させるワイルドカード。</param>
        public Wildcard(string pattern)
        {
            _pattern = pattern;
            _bodyRegex = new Regex(CreateRegexPattern(pattern));
        }

        /// <summary>指定したワイルドカードの <see cref="Wildcard"/> クラスの新しいインスタンスを初期化およびコンパイルします。</summary>
        /// <param name="pattern">一致させるワイルドカード。</param>
        /// <param name="options">ビットごとの OR 演算による <see cref="System.Text.RegularExpressions.RegexOptions"/> 列挙値の組み合わせ。</param>
        public Wildcard(string pattern, RegexOptions options)
        {
            _pattern = pattern;
            _bodyRegex = new Regex(CreateRegexPattern(pattern), CreateRegexOptions(options));
        }

        /// <summary>指定したワイルドカードの <see cref="Wildcard"/> クラスの新しいインスタンスを初期化およびコンパイルします。</summary>
        /// <param name="pattern">一致させるワイルドカード。</param>
        /// <param name="replacement">置換文字列。</param>
        public Wildcard(string pattern, string replacement)
        {
            _pattern = pattern;
            _replacement = replacement;
            _bodyRegex = new Regex(CreateRegexPattern(pattern));
        }

        /// <summary>指定したワイルドカードの <see cref="Wildcard"/> クラスの新しいインスタンスを初期化およびコンパイルします。</summary>
        /// <param name="pattern">一致させるワイルドカード。</param>
        /// <param name="replacement">置換文字列。</param>
        /// <param name="options">ビットごとの OR 演算による <see cref="System.Text.RegularExpressions.RegexOptions"/> 列挙値の組み合わせ。</param>
        public Wildcard(string pattern, string replacement, RegexOptions options)
        {
            _pattern = pattern;
            _replacement = replacement;
            _bodyRegex = new Regex(CreateRegexPattern(pattern), CreateRegexOptions(options));
        }

        private static string CreateRegexPattern(string pattern)
        {
            return "^" + UnescapeRegexChilda.Replace(WildToRegex.Replace(
                     pattern,
                     m => m.Value == "*" ? "(.*)" : m.Value == "?" ? "(.)" : Regex.Escape(m.Value)
                     ), @"\1") + "$";
        }

        private static RegexOptions CreateRegexOptions(RegexOptions options)
        {
            return options & ~(RegexOptions.ECMAScript) & ~(RegexOptions.ExplicitCapture);
        }

        /// <summary>コンパイルされたワイルドカードの現在の静的キャッシュ内の最大エントリ数を取得または設定します。</summary>
        public static int CacheSize
        {
            get { return Regex.CacheSize; }
            set { Regex.CacheSize = value; }
        }

        /// <summary>コンストラクタに渡されたオプションを返します。</summary>
        public RegexOptions Options
        {
            get { return _bodyRegex.Options; }
        }

        /// <summary>ワイルドカードが右から左の方向に検索するかどうかを示す値を取得します。</summary>
        public bool RightToLeft
        {
            get { return _bodyRegex.RightToLeft; }
        }

        /// <summary>メタ文字 (~、*、?) をエスケープ コードに置き換えることにより、それらのメタ文字をエスケープします。</summary>
        /// <param name="str">変換対象のテキストを含んでいる入力文字列。</param>
        /// <returns>エスケープされた形に変換されたメタ文字を含んでいる文字列。</returns>
        public static string Escape(string str)
        {
            return EscapeChilda.Replace(str, @"~\1");
        }

        /// <summary>入力文字列内にエスケープされた文字がある場合は、そのエスケープを解除します。</summary>
        /// <param name="str">変換対象のテキストを含んでいる入力文字列。</param>
        /// <returns>エスケープされない形式に変換された、エスケープされた文字を含む文字列。</returns>
        public static string Unescape(string str)
        {
            return UnescapeChilda.Replace(str, @"\1");
        }

        /// <summary>指定されたワイルドカードと一致する対象が入力文字列内で見つかったかどうかを示します。</summary>
        public bool IsMatch(string input)
        {
            return _bodyRegex.IsMatch(input);
        }

        /// <summary>指定されたワイルドカードと一致する対象が入力文字列内で見つかったかどうかを示します。</summary>
        public bool IsMatch(string input, int startat)
        {
            return _bodyRegex.IsMatch(input, startat);
        }

        /// <summary>指定されたワイルドカードと一致する対象が入力文字列内で見つかったかどうかを示します。</summary>
        public static bool IsMatch(string input, string pattern)
        {
            return Regex.IsMatch(input, CreateRegexPattern(pattern));
        }

        /// <summary>指定されたワイルドカードと一致する対象が入力文字列内で見つかったかどうかを示します。</summary>
        public static bool IsMatch(string input, string pattern, RegexOptions options)
        {
            return Regex.IsMatch(input, CreateRegexPattern(pattern), CreateRegexOptions(options));
        }

        /// <summary>指定した入力文字列内で、ワイルドカードと一致する対象を 1 つ検索します。</summary>
        public WildMatch Match(string input)
        {
            return new WildMatch(_bodyRegex.Match(input));
        }

        /// <summary>指定した入力文字列内で、ワイルドカードと一致する対象を 1 つ検索します。</summary>
        public WildMatch Match(string input, int startat)
        {
            return new WildMatch(_bodyRegex.Match(input, startat));
        }

        /// <summary>指定した入力文字列内で、ワイルドカードと一致する対象を 1 つ検索します。</summary>
        public static WildMatch Match(string input, string pattern)
        {
            return new WildMatch(Regex.Match(input, CreateRegexPattern(pattern)));
        }

        /// <summary>指定した入力文字列内で、ワイルドカードと一致する対象を 1 つ検索します。</summary>
        public WildMatch Match(string input, int beginning, int length)
        {
            return new WildMatch(_bodyRegex.Match(input, beginning, length));
        }

        /// <summary>指定した入力文字列内で、ワイルドカードと一致する対象を 1 つ検索します。</summary>
        public static WildMatch Match(string input, string pattern, RegexOptions options)
        {
            return new WildMatch(Regex.Match(input, CreateRegexPattern(pattern), CreateRegexOptions(options)));
        }

        /// <summary>指定した入力文字列内で、ワイルドカードと一致する対象をすべて検索します。</summary>
        public WildMatchCollection Matches(string input)
        {
            return new WildMatchCollection(_bodyRegex.Matches(input));
        }

        /// <summary>指定した入力文字列内で、ワイルドカードと一致する対象をすべて検索します。</summary>
        public WildMatchCollection Matches(string input, int startat)
        {
            return new WildMatchCollection(_bodyRegex.Matches(input, startat));
        }

        /// <summary>指定した入力文字列内で、ワイルドカードと一致する対象をすべて検索します。</summary>
        public static WildMatchCollection Matches(string input, string pattern)
        {
            return new WildMatchCollection(Regex.Matches(input, CreateRegexPattern(pattern)));
        }

        /// <summary>指定した入力文字列内で、ワイルドカードと一致する対象をすべて検索します。</summary>
        public static WildMatchCollection Matches(string input, string pattern, RegexOptions options)
        {
            return new WildMatchCollection(Regex.Matches(input, CreateRegexPattern(pattern), CreateRegexOptions(options)));
        }

        /// <summary>指定した入力文字列内でワイルドカードに一致するすべての文字列を置換します。</summary>
        public string Replace(string input, WildMatchEvaluator evaluator)
        {
            return _bodyRegex.Replace(input, m => evaluator(new WildMatch(m)));
        }

        /// <summary>指定した入力文字列内でワイルドカードに一致するすべての文字列を置換します。</summary>
        public string Replace(string input, string replacement)
        {
            return _bodyRegex.Replace(input, replacement);
        }

        /// <summary>指定した入力文字列内でワイルドカードに一致するすべての文字列を置換します。</summary>
        public string Replace(string input, WildMatchEvaluator evaluator, int count)
        {
            return _bodyRegex.Replace(input, m => evaluator(new WildMatch(m)), count);
        }

        /// <summary>指定した入力文字列内でワイルドカードに一致するすべての文字列を置換します。</summary>
        public string Replace(string input, string replacement, int count)
        {
            return _bodyRegex.Replace(input, replacement, count);
        }

        /// <summary>指定した入力文字列内でワイルドカードに一致するすべての文字列を置換します。</summary>
        public string Replace(string input)
        {
            return _bodyRegex.Replace(input, _replacement);
        }

        /// <summary>指定した入力文字列内でワイルドカードに一致するすべての文字列を置換します。</summary>
        public string Replace(string input, int startat)
        {
            return _bodyRegex.Replace(input, _replacement, startat);
        }

        /// <summary>マッチングパタン文字列を取得します。</summary>
        public string Pattern
        {
            get { return _pattern; }
        }

        /// <summary>置換文字列を取得します。</summary>
        public string Replacement
        {
            get { return _replacement; }
        }

        /// <summary>指定した入力文字列内でワイルドカードに一致するすべての文字列を置換します。</summary>
        public static string Replace(string input, string pattern, WildMatchEvaluator evaluator)
        {
            return Regex.Replace(input, CreateRegexPattern(pattern), m => evaluator(new WildMatch(m)));
        }

        /// <summary>指定した入力文字列内でワイルドカードに一致するすべての文字列を置換します。</summary>
        public static string Replace(string input, string pattern, string replacement)
        {
            return Regex.Replace(input, CreateRegexPattern(pattern), replacement);
        }

        /// <summary>指定した入力文字列内でワイルドカードに一致するすべての文字列を置換します。</summary>
        public string Replace(string input, WildMatchEvaluator evaluator, int count, int startat)
        {
            return _bodyRegex.Replace(input, m => evaluator(new WildMatch(m)), count, startat);
        }

        /// <summary>指定した入力文字列内でワイルドカードに一致するすべての文字列を置換します。</summary>
        public string Replace(string input, string replacement, int count, int startat)
        {
            return _bodyRegex.Replace(input, replacement, count, startat);
        }

        /// <summary>指定した入力文字列内でワイルドカードに一致するすべての文字列を置換します。</summary>
        public static string Replace(string input, string pattern, WildMatchEvaluator evaluator, RegexOptions options)
        {
            return Regex.Replace(input, CreateRegexPattern(pattern), m => evaluator(new WildMatch(m)), options);
        }

        /// <summary>指定した入力文字列内でワイルドカードに一致するすべての文字列を置換します。</summary>
        public static string Replace(string input, string pattern, string replacement, RegexOptions options)
        {
            return Regex.Replace(input, CreateRegexPattern(pattern), replacement, options);
        }

        /// <summary>ワイルドカードで、指定した入力文字列を分割します。</summary>
        public string[] Split(string input)
        {
            return _bodyRegex.Split(input);
        }

        /// <summary>ワイルドカードで、指定した入力文字列を分割します。</summary>
        public string[] Split(string input, int count)
        {
            return _bodyRegex.Split(input, count);
        }

        /// <summary>ワイルドカードで、指定した入力文字列を分割します。</summary>
        public static string[] Split(string input, string pattern)
        {
            return Regex.Split(input, CreateRegexPattern(pattern));
        }

        /// <summary>ワイルドカードで、指定した入力文字列を分割します。</summary>
        public string[] Split(string input, int count, int startat)
        {
            return _bodyRegex.Split(input, count, startat);
        }

        /// <summary>ワイルドカードで、指定した入力文字列を分割します。</summary>
        public static string[] Split(string input, string pattern, RegexOptions options)
        {
            return Regex.Split(input, CreateRegexPattern(pattern), options);
        }

        /// <summary>コンストラクタに渡されたワイルドカードを返します。</summary>
        public override string ToString()
        {
            return _pattern ?? string.Empty;
        }
    }
}
